﻿/****************************** Module Header ******************************\ 
 * Module Name:  CropBitmap.cs 
 * Project:      CSWindowsStoreAppCropBitmap 
 * Copyright (c) Microsoft Corporation. 
 *  
 * This class is used to get and save cropped image. 
 *  
 * To crop a bitmap, we can follow these steps: 
 *  
 * 1. Read the original image to a IRandomAccessStream 
 * 2. Create a decoder from the stream. With the decoder, we can get the 
 *    properties of the image. 
 * 3. Use BitmapTransform to define the region to crop, and then get the pixel 
 *    data in the region. 
 *    If we also want to scale the image, we can set the ScaledWidth and  
 *    ScaledHeight properties of the BitmapTransform. 
 * 4. To get a cropped bitmap directly, we write the pixel data to a WriteableBitmap. 
 *    To save the cropped bitmap to a local file, we can use BitmapEncoder. 
 *   
 * This source is subject to the Microsoft Public License. 
 * See http://www.microsoft.com/en-us/openness/licenses.aspx#MPL 
 * All other rights reserved. 
 *  
 * THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,  
 * EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED  
 * WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. 
\***************************************************************************/

using System;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;

namespace JiveMessenger.Utilities
{
    public class CropBitmap
    {

        async public static Task<WriteableBitmap> GetCroppedBitmapAsync(IRandomAccessStream imgData,
         Point startPoint, Size cropSize, double scale)
        {
            if (double.IsNaN(scale) || double.IsInfinity(scale))
            {
                scale = 1;
            }

            // Convert start point and size to integer. 
            uint startPointX = (uint)Math.Floor(startPoint.X * scale);
            uint startPointY = (uint)Math.Floor(startPoint.Y * scale);
            uint height = (uint)Math.Floor(cropSize.Height * scale);
            uint width = (uint)Math.Floor(cropSize.Width * scale);

            using (IRandomAccessStream stream = imgData)
            {

                // Create a decoder from the stream. With the decoder, we can get  
                // the properties of the image. 
                BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);

                // The scaledSize of original image. 
                uint scaledWidth = (uint)Math.Floor(decoder.PixelWidth * scale);
                uint scaledHeight = (uint)Math.Floor(decoder.PixelHeight * scale);


                // Refine the start point and the size.  
                if (startPointX + width > scaledWidth)
                {
                    startPointX = scaledWidth - width;
                }

                if (startPointY + height > scaledHeight)
                {
                    startPointY = scaledHeight - height;
                }

                // Get the cropped pixels. 
                byte[] pixels = await GetPixelData(decoder, startPointX, startPointY, width, height,
                    scaledWidth, scaledHeight);

                // Stream the bytes into a WriteableBitmap 
                WriteableBitmap cropBmp = new WriteableBitmap((int)width, (int)height);
                Stream pixStream = cropBmp.PixelBuffer.AsStream();
                pixStream.Write(pixels, 0, (int)(width * height * 4));

                return cropBmp;
            }

        }
        /// <summary> 
        /// Get a cropped bitmap from a image file. 
        /// </summary> 
        /// <param name="originalImageFile"> 
        /// The original image file. 
        /// </param> 
        /// <param name="startPoint"> 
        /// The start point of the region to be cropped. 
        /// </param> 
        /// <param name="corpSize"> 
        /// The size of the region to be cropped. 
        /// </param> 
        /// <returns> 
        /// The cropped image. 
        /// </returns> 
        async public static Task<WriteableBitmap> GetCroppedBitmapAsync(StorageFile originalImageFile,
            Point startPoint, Size corpSize, double scale)
        {
            if (double.IsNaN(scale) || double.IsInfinity(scale))
            {
                scale = 1;
            }

            // Convert start point and size to integer. 
            uint startPointX = (uint)Math.Floor(startPoint.X * scale);
            uint startPointY = (uint)Math.Floor(startPoint.Y * scale);
            uint height = (uint)Math.Floor(corpSize.Height * scale);
            uint width = (uint)Math.Floor(corpSize.Width * scale);

            using (IRandomAccessStream stream = await originalImageFile.OpenReadAsync())
            {

                // Create a decoder from the stream. With the decoder, we can get  
                // the properties of the image. 
                BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);

                // The scaledSize of original image. 
                uint scaledWidth = (uint)Math.Floor(decoder.PixelWidth * scale);
                uint scaledHeight = (uint)Math.Floor(decoder.PixelHeight * scale);


                // Refine the start point and the size.  
                if (startPointX + width > scaledWidth)
                {
                    startPointX = scaledWidth - width;
                }

                if (startPointY + height > scaledHeight)
                {
                    startPointY = scaledHeight - height;
                }

                // Get the cropped pixels. 
                byte[] pixels = await GetPixelData(decoder, startPointX, startPointY, width, height,
                    scaledWidth, scaledHeight);

                // Stream the bytes into a WriteableBitmap 
                WriteableBitmap cropBmp = new WriteableBitmap((int)width, (int)height);
                Stream pixStream = cropBmp.PixelBuffer.AsStream();
                pixStream.Write(pixels, 0, (int)(width * height * 4));

                return cropBmp;
            }

        }
        async public static Task<WriteableBitmap> CreateWritableBitmap(StorageFile originalImageFile)
        {
         using (IRandomAccessStream stream = await originalImageFile.OpenReadAsync())
         {
             return await CreateWritableBitmap(stream);
         }
        }
        async public static Task<WriteableBitmap> CreateWritableBitmap(IRandomAccessStream stream)
        {
            IRandomAccessStream originalImgFileStream = stream;
            
                // Create a decoder from the stream. With the decoder, we can get  
                // the properties of the image. 
                BitmapDecoder decoder = await BitmapDecoder.CreateAsync(originalImgFileStream);

                uint width = decoder.PixelWidth;
                uint height = decoder.PixelHeight;
                // Get the cropped pixels. 
                byte[] pixels = await GetPixelData(decoder, 0, 0, width, height,
                    decoder.PixelWidth, decoder.PixelHeight);

                // Stream the bytes into a WriteableBitmap 
                WriteableBitmap cropBmp = new WriteableBitmap((int)width, (int)height);
                Stream pixStream = cropBmp.PixelBuffer.AsStream();
                pixStream.Write(pixels, 0, (int)(width * height * 4));

                return cropBmp;
            
        }
        /// <summary> 
        /// Save the cropped bitmap to a image file. 
        /// </summary> 
        /// <param name="originalImageFile"> 
        /// The original image file. 
        /// </param> 
        /// <param name="newImageFile"> 
        /// The target file. 
        /// </param> 
        /// <param name="startPoint"> 
        /// The start point of the region to be cropped. 
        /// </param> 
        /// <param name="cropSize"> 
        /// The size of the region to be cropped. 
        /// </param> 
        /// <returns> 
        /// Whether the operation is successful. 
        /// </returns> 
        async public static Task SaveCroppedBitmapAsync(StorageFile originalImageFile, StorageFile newImageFile,
            Point startPoint, Size cropSize)
        {

            // Convert start point and size to integer. 
            uint startPointX = (uint)Math.Floor(startPoint.X);
            uint startPointY = (uint)Math.Floor(startPoint.Y);
            uint height = (uint)Math.Floor(cropSize.Height);
            uint width = (uint)Math.Floor(cropSize.Width);

            using (IRandomAccessStream originalImgFileStream = await originalImageFile.OpenReadAsync())
            {


                // Create a decoder from the stream. With the decoder, we can get  
                // the properties of the image. 
                BitmapDecoder decoder = await BitmapDecoder.CreateAsync(originalImgFileStream);

                // Refine the start point and the size.  
                if (startPointX + width > decoder.PixelWidth)
                {
                    startPointX = decoder.PixelWidth - width;
                }

                if (startPointY + height > decoder.PixelHeight)
                {
                    startPointY = decoder.PixelHeight - height;
                }

                // Get the cropped pixels. 
                byte[] pixels = await GetPixelData(decoder, startPointX, startPointY, width, height,
                    decoder.PixelWidth, decoder.PixelHeight);

                using (IRandomAccessStream newImgFileStream = await newImageFile.OpenAsync(FileAccessMode.ReadWrite))
                {

                    Guid encoderID = Guid.Empty;

                    switch (newImageFile.FileType.ToLower())
                    {
                        case ".png":
                            encoderID = BitmapEncoder.PngEncoderId;
                            break;
                        case ".bmp":
                            encoderID = BitmapEncoder.BmpEncoderId;
                            break;
                        default:
                            encoderID = BitmapEncoder.JpegEncoderId;
                            break;
                    }

                    // Create a bitmap encoder 

                    BitmapEncoder bmpEncoder = await BitmapEncoder.CreateAsync(
                        encoderID,
                        newImgFileStream);

                    // Set the pixel data to the cropped image. 
                    bmpEncoder.SetPixelData(
                        BitmapPixelFormat.Bgra8,
                        BitmapAlphaMode.Straight,
                        width,
                        height,
                        decoder.DpiX,
                        decoder.DpiY,
                        pixels);

                    // Flush the data to file. 
                    await bmpEncoder.FlushAsync();
                }
            }

        }

        /// <summary> 
        /// Use BitmapTransform to define the region to crop, and then get the pixel data in the region 
        /// </summary> 
        /// <returns></returns> 
        async static private Task<byte[]> GetPixelData(BitmapDecoder decoder, uint startPointX, uint startPointY,
            uint width, uint height)
        {
            return await GetPixelData(decoder, startPointX, startPointY, width, height,
                decoder.PixelWidth, decoder.PixelHeight);
        }
        /// <summary>
        /// Use BitmapTransform to define the region to Resize, and then get the pixel data in the region 
        /// </summary>
        /// <param name="decoder"></param>
        /// <param name="scaledWidth"></param>
        /// <param name="scaledHeight"></param>
        /// <returns></returns>
        async static private Task<byte[]> GetPixelData(BitmapDecoder decoder, uint scaledWidth, uint scaledHeight)
        {
            return await GetPixelData(decoder, 0, 0, 0, 0,
                decoder.PixelWidth, decoder.PixelHeight, true);
        }
        /// <summary> 
        /// Use BitmapTransform to define the region to crop, and then get the pixel data in the region. 
        /// If you want to get the pixel data of a scaled image, set the scaledWidth and scaledHeight 
        /// of the scaled image. 
        /// </summary> 
        /// <returns></returns> 
        async static private Task<byte[]> GetPixelData(BitmapDecoder decoder, uint startPointX, uint startPointY,
            uint width, uint height, uint scaledWidth, uint scaledHeight, bool doResize = false)
        {

            BitmapTransform transform = new BitmapTransform();

            if (!doResize)
            {
                BitmapBounds bounds = new BitmapBounds();
                bounds.X = startPointX;
                bounds.Y = startPointY;
                bounds.Height = height;
                bounds.Width = width;

                transform.Bounds = bounds;
            }
            transform.ScaledWidth = scaledWidth;
            transform.ScaledHeight = scaledHeight;

            // Get the cropped pixels within the bounds of transform. 
            PixelDataProvider pix = await decoder.GetPixelDataAsync(
                BitmapPixelFormat.Bgra8,
                BitmapAlphaMode.Straight,
                transform,
                ExifOrientationMode.RespectExifOrientation,
                ColorManagementMode.ColorManageToSRgb);
            byte[] pixels = pix.DetachPixelData();
            return pixels;
        }

    }
}